#include <stdio.h>
#include <stdbool.h>
#include <assert.h>
#include <sys/time.h>

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libswresample/swresample.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_video.h>
#include <SDL2/SDL_render.h>
#include <SDL2/SDL_rect.h>

#include <libavutil/imgutils.h>

#define SDL_USEREVENT_REFRESH (SDL_USEREVENT + 1)

/**
 * @brief 构建一个简单的队列，使用单向链表为基础单元。
 * 如同链表一样，在尾部添加数据。
 * 不同不断的修改队列的头部指针来，实现先入先出的操作
 * 
 */
typedef struct packet_queue_t
{
    AVPacketList *first_pkt;
    AVPacketList *last_pkt;
    int nb_packets;
    int size;
    SDL_mutex *mutex;
    SDL_cond *cond;
} packet_queue_t;

static packet_queue_t s_audio_pkt_queue;
static packet_queue_t s_video_pkt_queue;

static bool s_adecode_finished = false; // 解码完毕
static bool s_vdecode_finished = false; // 解码完毕

void packet_queue_init(packet_queue_t *q)
{
    memset(q, 0x00, sizeof(packet_queue_t));
    q->mutex = SDL_CreateMutex();
    q->cond = SDL_CreateCond();
}

int packet_queue_push(packet_queue_t *q, AVPacket *pkt)
{
}

int packet_queue_num(packet_queue_t *q)
{
}

/**
 * @brief 通过 interval 参数传入当前的 timer interval ，返回下一次 timer 的 interval，返回0 表示取消定时器
 * 定时器超过时会调用此回调函数，产生 FF_REFRESH_EVENT 事件，添加到事件队列
 * 
 * @param interval 
 * @param opaque 
 * @return uint32_t 
 */
static uint32_t sdl_time_cb_refresh(uint32_t interval, void *opaque)
{
    SDL_Event sdl_event;
    sdl_event.type = SDL_USEREVENT_REFRESH;
    SDL_PushEvent(&sdl_event);
    return interval;
}

int video_thread(void *arg)
{
    AVCodecContext *p_codec_ctx = (AVCodecContext *)arg;

    AVFrame *p_frm_raw = NULL;
    AVFrame *p_frm_yuv = NULL;
    AVPacket *p_packet = NULL;
    struct SwsContext *sws_ctx = NULL;
    int buf_size;
    uint8_t *buffer = NULL;
    SDL_Window *screen;
    SDL_Renderer *sdl_renderer;
    SDL_Texture *sdl_texture;
    SDL_Rect sdl_rect;
    SDL_Thread *sdl_thread;
    SDL_Event sdl_event;

    int ret = 0;
    int res = -1;

    p_packet = (AVPacket *)av_malloc(sizeof(AVPacket));
    if (p_packet == NULL)
    {
        av_log(NULL, AV_LOG_ERROR, " av_malloc failed \n");
        goto exit;
    }

    p_frm_raw = av_frame_alloc();
    if (p_frm_raw == NULL)
    {
        av_log(NULL, AV_LOG_ERROR, " av_malloc failed \n");
        goto exit;
    }

    p_frm_yuv = av_frame_alloc();
    if (p_frm_yuv == NULL)
    {
        av_log(NULL, AV_LOG_ERROR, " av_malloc failed \n");
        goto exit;
    }

    /**
     * @brief 现在要为 p_frm_yuv 的 data buffer 分配内存，用于存放 sws_scale()中目的帧视频数据
     *  p_frm_raw 的 data buffer 由 av_read_frame 分配，因此不需要手动分配
     *  p_frm_yuv 的 data buffer 无处分配，因此在此手动分配。
     */
    buf_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,
                                        p_codec_ctx->width,
                                        p_codec_ctx->height, 1);
    buffer = (uint8_t * )av_malloc(buf_size);
exit:
    if (p_packet)
    {
        av_free(p_packet);
    }

    if (p_frm_raw)
    {
        av_frame_free(&p_frm_raw);
    }
    if (p_frm_yuv)
    {
        av_frame_free(&p_frm_yuv);
    }
}

int open_audio_stream(AVFormatContext *p_fmt_ctx, AVCodecContext *p_codec_ctx, int steam_idx)
{
}

int open_video_stream(AVFormatContext *p_fmt_ctx, AVCodecContext *p_codec_ctx, int stream_idx)
{
    AVCodecParameters *p_codec_par = NULL;
    AVCodec *p_codec = NULL;
    int ret;

    packet_queue_init(&s_video_pkt_queue);

    p_codec_par = p_fmt_ctx->streams[stream_idx]->codecpar;

    p_codec = avcodec_find_decoder(p_codec_par->codec_id);
    if (p_codec == NULL)
    {
        printf("Cann't find codec !\n");
        return -1;
    }

    p_codec_ctx = avcodec_alloc_context3(p_codec);
    if (p_codec_ctx == NULL)
    {
        printf("avcodec_alloc_context3 failed \n");
        return -1;
    }

    ret = avcodec_parameters_to_context(p_codec_ctx, p_codec_par);
    if (ret < 0)
    {
        printf("avcodec_parameters_to_context failed [%s]\n", av_err2str(ret));
        return -1;
    }

    int temp_num = p_fmt_ctx->streams[stream_idx]->avg_frame_rate.num;
    int temp_den = p_fmt_ctx->streams[stream_idx]->avg_frame_rate.den;
    int frame_rate = (temp_den > 0) ? temp_num / temp_den : 25;      //1秒 25 帧
    int interval = (temp_num > 0) ? temp_den * 1000 / temp_num : 40; //40秒 渲染 1帧

    printf("frame rate %d FPS, refresh interval %d ms \n", frame_rate, interval);

    SDL_AddTimer(interval, sdl_time_cb_refresh, NULL);

    SDL_CreateThread(video_thread, "video thread", p_codec_ctx);

    return 0;
}

int main(int argc, char const *argv[])
{
    // Initalizing these to NULL prevents segfaults!
    AVFormatContext *p_fmt_ctx = NULL;
    AVCodecContext *p_acodec_ctx = NULL;
    AVCodecContext *p_vcodec_ctx = NULL;
    AVPacket *p_packet = NULL;
    int i = 0;
    int a_idx = -1;
    int v_idx = -1;
    int ret = -1;
    int res = -1;

    if (argc < 2)
    {
        printf("argc < 2 \n");
        return -1;
    }

    if (SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO | SDL_INIT_TIMER))
    {
        printf("SDL_Init() failed: %s\n", SDL_GetError());
        res = -1;
        goto exit2;
    }

    ret = avformat_open_input(p_fmt_ctx, argv[1], NULL, NULL);
    if (ret != 0)
    {
        printf("avformat_open_input failed:[%s] \n ", av_err2str(ret));
        goto exit0;
    }

    ret = avformat_find_stream_info(p_fmt_ctx, NULL);
    if (ret < 0)
    {
        printf("avformat_find_stream_info failed:[%s] \n", av_err2str(ret));
        goto exit0;
    }

    av_dump_format(p_fmt_ctx, 0, argv[1], 0);

    a_idx = -1;
    v_idx = -1;
    for (size_t i = 0; i < p_fmt_ctx->nb_streams; i++)
    {
        if ((p_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) && (a_idx == -1))
        {
            a_idx = i;
            printf("Find a audio stream ,index %d \n", a_idx);
            open_audio_stream(p_fmt_ctx, p_acodec_ctx, a_idx);
        }
        else if ((p_fmt_ctx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) && (v_idx == -1))
        {
            v_idx = i;
            printf("Find a video stream , index [%d]\n", v_idx);
            open_video_stream(p_fmt_ctx, p_vcodec_ctx, v_idx);
        }

        if (a_idx != -1 && v_idx != -1)
        {
            break;
        }
    }

    if (a_idx == -1 && v_idx == -1)
    {
        printf("Cann't find any audio/video stream\n");
        res = -1;
        goto exit1;
    }

    p_packet = (AVPacket *)av_malloc(sizeof(AVPacket));
    if (p_packet == NULL)
    {
        printf(" av_malloc failed \n");
        res = -1;
        goto exit2;
    }

    while (1)
    {
        if (packet_queue_num(&s_video_pkt_queue) > 100 ||
            packet_queue_num(&s_audio_pkt_queue) > 500)
        {
            SDL_Delay(10000);
            continue;
        }

        ret = av_read_frame(p_fmt_ctx, p_packet);
        if (ret < 0)
        {
            if (ret == AVERROR_EOF || avio_feof(p_fmt_ctx->pb))
            {
                // 输入文件已读完，发送NULL packet以冲洗(flush)解码器，否则解码器中缓存的帧取不出来
                printf("read end of file \n");
                p_packet->data = NULL;
                p_packet->size = 0;
                if (v_idx != -1)
                {
                    printf("push a flush packet into video queue \n");
                    packet_queue_push(&s_video_pkt_queue, p_packet);
                }

                if (a_idx != -1)
                {
                    printf(" push a flush packet into audio queue \n");
                    packet_queue_push(&s_audio_pkt_queue, p_packet);
                }

                break;
            }
            else
            {
                printf("read unexpected error \n");
                goto exit3;
            }
        }
        else
        {
            if (p_packet->stream_index == a_idx)
            {
                packet_queue_push(&s_audio_pkt_queue, p_packet);
            }
            else if (p_packet->stream_index == v_idx)
            {
                packet_queue_push(&s_video_pkt_queue, p_packet);
            }
            else
            {
                av_packet_unref(p_packet);
            }
        }
    }

    printf("loop read frame end \n");
    while (((a_idx >= 0) && (!s_adecode_finished)) ||
           ((v_idx >= 0) && (!s_vdecode_finished)))
    {
        SDL_Delay(100);
    }
    printf("paly finished . exit now... \n");

    SDL_Delay(200);

exit3:
    SDL_Quit();
exit2:
    av_packet_unref(p_packet);
    if (p_packet)
    {
        av_free(p_packet);
    }
exit1:
    avformat_close_input(&p_fmt_ctx);
exit0:
    return res;
}
//https://hub.fastgit.org/leichn/exercises.git